/*
 * This file is part of Cadmium.
 * Copyright (C) 2007-2010 Xavier Clerc.
 *
 * Cadmium is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * Cadmium is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package fr.x9c.cadmium.util;

import java.util.LinkedList;
import java.util.List;

import fr.x9c.cadmium.kernel.Fail;
import fr.x9c.cadmium.kernel.Fatal;
import fr.x9c.cadmium.kernel.Value;

/**
 * This class implements a Cadmium stack, based on
 * {@link java.util.LinkedList}. <br/>
 * This implementation is <b>NOT</b> synchronized. Hence, use by multiple
 * threads may result in contract violation (<i>e.g.</i> stack may contain
 * more elements than specified by maximum size).
 *
 * @see java.util.LinkedList
 *
 * @author <a href="mailto:cadmium@x9c.fr">Xavier Clerc</a>
 * @version 1.0
 * @since 1.0
 */
public final class ListStack implements Stack {

    /** Stack elements, first element of list being top of the stack. */
    private final List<Value> elements;

    /** Maximum number of elements that the stack may contain. */
    private final int maxSize;

    /**
     * Constructs an empty stack.
     * @param maxSz maximum stack size - should be strictly positive
     */
    public ListStack(final int maxSz) {
        assert maxSz > 0 : "maxSz should be > 0";
        this.elements = new LinkedList<Value>();
        this.maxSize = maxSz;
    } // end constructor(int)

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean isEmpty() {
        return this.elements.isEmpty();
    } // end method 'isEmpty()'

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean isFull() {
        return this.elements.size() >= this.maxSize;
    } // end method 'isFull()'

    /**
     * {@inheritDoc}
     */
    @Override
    public int size() {
        return this.elements.size();
    } // end method 'size()'

    /**
     * {@inheritDoc}
     */
    @Override
    public int maxSize() {
        return this.maxSize;
    } // end method 'maxSize()'

    /**
     * {@inheritDoc}
     */
    @Override
    public void push(final Value v) throws Fail.Exception {
        assert v != null : "null v";
        if (isFull()) {
            Fail.raiseStackOverflow();
        } else {
            this.elements.add(0, v);
        } // end if/else
    } // end method 'push(Value)'

    /**
     * {@inheritDoc}
     */
    @Override
    public Value pop() throws Fatal.Exception {
        if (isEmpty()) {
            Fatal.raiseStackUnderflow();
            return null; // never reached
        } else {
            return this.elements.remove(0);
        } // end if/else
    } // end method 'pop()'

    /**
     * {@inheritDoc}
     */
    @Override
    public void pop(final int n) throws Fatal.Exception {
        assert n >= 0 : "n should be >= 0";
        if (this.elements.size() >= n) {
            for (int i = 0; i < n; i++) {
                this.elements.remove(0);
            } // end for
        } else {
            this.elements.clear();
            Fatal.raiseStackUnderflow();
        } // end if/else
    } // end method 'pop(int)'

    /**
     * {@inheritDoc}
     */
    @Override
    public Value peek(final int idx) throws Fatal.Exception {
        assert idx >= 0 : "idx should be >= 0";
        if (idx < this.elements.size()) {
            return this.elements.get(idx);
        } else {
            Fatal.raiseStackUnderflow();
            return null; // never reached
        } // end if/else
    } // end method 'peek(int)'

    /**
     * {@inheritDoc}
     */
    @Override
    public void assign(final int idx, final Value v) throws Fatal.Exception {
        assert idx >= 0 : "idx should be >= 0";
        assert v != null : "null v";
        if (idx < this.elements.size()) {
            this.elements.set(idx, v);
        } else {
            Fatal.raiseStackUnderflow();
        } // end if/else
    } // end method 'assign(int, Value)'

    /**
     * {@inheritDoc}
     */
    @Override
    public Value[] popSlice(final int n) throws Fatal.Exception {
        assert n >= 0 : "n should be >= 0";
        if (this.elements.size() >= n) {
            final Value[] res = new Value[n];
            for (int i = 0; i < n; i++) {
                res[n - 1 - i] = this.elements.remove(0);
            } // end for
            return res;
        } else {
            this.elements.clear();
            Fatal.raiseStackUnderflow();
            return new Value[0]; // never reached
        } // end if/else
    } // end method 'popSlice(int)'

    /**
     * {@inheritDoc}
     */
    @Override
    public void pushSlice(final Value[] s) throws Fail.Exception {
        assert s != null : "null s";
        final int len = s.length;
        for (int i = 0; i < len; i++) {
            push(s[i]);
        } // end for
    } // end method 'pushSlice(Value[])'

    /**
     * {@inheritDoc}
     */
    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder("ListStack [maxSize=");
        sb.append(this.maxSize);
        sb.append("]: ");
        sb.append(this.elements.toString());
        return sb.toString();
    } // end method 'toString()'

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean equals(final Object obj) {
        if (obj instanceof ListStack) {
            final ListStack that = (ListStack) obj;
            return (this.maxSize == that.maxSize)
                && this.elements.equals(that.elements);
        } else {
            return false;
        } // end if/else
    } // end method 'equals(Object)'

    /**
     * {@inheritDoc}
     */
    @Override
    public int hashCode() {
        return this.maxSize + this.elements.hashCode();
    } // end method 'hashCode()'

} // end class 'ListStack'
